#ifndef _SOCKETEUDP_H
#define _SOCKETEUDP_H

#ifdef DLL_EUDP_EX
   #define DLL_EUDP  __declspec(dllexport)   // export DLL information
#else
   #define DLL_EUDP  __declspec(dllimport)   // import DLL information
#endif 

#define BUFFER_MAX 1024

#define TIMEOUT         15000
#define SYNC             2000
#define RELIABLE_RESEND   500

#define MESSAGE_SIZE(length) (sizeof(char) + sizeof(EUDP_M) + sizeof(unsigned long) + (3 * sizeof(unsigned short) ) + (length * sizeof(char) ) )

#include <map>
#include <queue>
#include <string>
#include <vector>
#include <WinSock2.h>

//TO DO LIST
//Have EUDP automatically re-request reliable messages if they are received out of order
//Sort out reliable messages being eternally sent if connected to more than one client
//Ensure connections are being shut down correctly

enum EUDP_M //EUDP Messages
{
	EUDP_M_CONNECT = 0,
	EUDP_M_DISCONNECT,
	EUDP_M_SYNC,
	EUDP_M_MESSAGE,
	EUDP_M_MESSAGE_RELIABLE,
	EUDP_M_MESSAGE_RELIABLE_REQUEST,
	EUDP_M_MESSAGE_RELIABLE_CONFIRM,
	EUDP_M_EMPTY,
	EUDP_M_COUNT
};

enum EUDP_R //EUDP Return Values
{
	EUDP_R_OK = 0,
	EUDP_R_NO_CONNECTION,
	EUDP_R_NO_HANDSHAKE,
	EUDP_R_ERROR,
	EUDP_R_COUNT
};

class SocketEUDP
{
public:
	struct ConnectionInfo
	{
		ConnectionInfo()
		{
			lastSyncMessageTime = 0;
			reliableReference   = 0;
		}

		std::string    ip;
		DWORD          connectionTime;
		DWORD          lastMessageTime;
		DWORD          lastSyncMessageTime;
		unsigned short port;
		unsigned long  reliableReference;

		int shakeCount;
	};
	struct Message
	{
		Message()
		{
			length = 0;
			reference = 0;
		}

		char           checksum;
		EUDP_M         messageTypeEUDP;
		unsigned long  reference;
		unsigned short port;
		unsigned short messageType;
		unsigned short length;
		char           data[BUFFER_MAX];
	};
	struct ReliableInfo
	{
		int connectionReference;
		unsigned long reliableReference;
	};

	SocketEUDP() : m_socketIn(INVALID_SOCKET), m_socketOut(INVALID_SOCKET), m_autoConnect(true), m_connectionMapValue(0)
	{
		/* nothing */
	}

	bool CreateSocket(int port);

	EUDP_R ConnectTo(std::string ip, int port);
	EUDP_R Send(int reference, short messageType, void *data, size_t length, bool reliable);
	EUDP_R Disconnect(int reference);

	//TODO: Create interface for dealing with not automatically accepting all connections

	std::map<int, ConnectionInfo> GetConnections();
	std::string GetLastEvent();
	bool ConnectedTEMP();

	std::string    GetConnectionIp(int reference);
	unsigned short GetConnectionPort(int reference);
	unsigned short GetThisPort() { return m_port; }

	int GetMessageCount(); //Returns the number of messages waiting to be collected
	Message GetMessages(); //Returns messages in FIFO order, if called when no messages are waiting, returns a message marked EUDP_M_EMPTY
	
	void Update(); // Make sure to call this reguarly, however not too often or you're just slowing down your program, call only once or twice a frame

private:
	bool CreateInboundSocket(int port);
	bool CreateOutboundSocket();
	void UpdateInbound();
	void UpdateOutbound();

	char CreateChecksum(Message message);
	bool ValidateChecksum(Message message);

	EUDP_R SendReliableConfirmation(int reference, unsigned long reliableReference);

	void ConnectionAdd(std::string ip, int port, int shakeCount);
	int  ConnectionCheck(int reference); //Returns the handshake level of the connection or -1 if not found
	int  ConnectionReference(std::string ip, int port); //Returns the refernce of the specified ip and port, or -1 if not found
	void ConnectionRemove(int reference);

	SOCKET m_socketIn;
	SOCKET m_socketOut;

	unsigned short m_port;
	bool m_autoConnect;
	int m_connectionMapValue;

	std::map<ReliableInfo, Message>    m_messagesToSend;
	std::map<ReliableInfo, DWORD> m_messagesToSendTimer;
	std::queue<Message>             m_messagesToReceive;
	std::map<int, ConnectionInfo>         m_connections;

	std::queue<std::string> m_events;
};

bool operator < (const SocketEUDP::ReliableInfo a, const SocketEUDP::ReliableInfo b);

#endif